/*
 * Created on 18. sep.. 2006
 * Created by Tomas Ekeli
 */
package util;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import people.DuplicatePersonException;
import people.Group;
import people.MembershipManager;
import people.Person;
import people.PersonNotFoundException;
import placing.IllegalNodeException;
import placing.Mapper;
import placing.Place;
import placing.PlaceNotFoundException;
import placing.Reservation;
import placing.ReservationConflictException;
import placing.Room;
import placing.RoomException;
import placing.RoomManager;

/**
 * 
 * Handles the export to and the import from files. Expects XML -files.
 * 
 * @author Tomas Ekeli
 *
 */
public class FileHandler {
	//Element-tags for the Rooms
	private static final String ROOM_LIST 			= "ROOMLIST";
	
	private static final String ROOM 				= "ROOM";
	private static final String ROOM_ID 			= "ID";
	private static final String ROOM_NAME 			= "NAME";
	
	private static final String PLACE 				= "PLACE";
	private static final String PLACE_ID 			= "ID";
	private static final String PLACE_FREE 			= "FREE";
	private static final String PLACE_RESERVED 		= "RESERVED";
	private static final String PLACE_PREF 			= "PREFERRED";
	
	private static final String HISTORY 			= "HISTORY";
	
	private static final String PREVIOUS_OWNER 		= "PREV_OWN";
	private static final String PREV_OWN_SEQ 		= "SEQUENCE";
	private static final String PREV_OWN_ID			= "ID";
	
	private static final String RESERVATION_LIST 	= "RES_LIST";
	private static final String RESERVATION 		= "RES";
	
	
	private static final String RESERVATION_PERSON 	= "PERSON";
	private static final String RES_GROUP 			= "GROUP";
	private static final String RES_PERSON_ID		= "ID";
	private static final String RES_GROUP_ID 		= "ID";

	//Element tags for the Persons
	private static final String PERSON_LIST 		= "PERSON_LIST";

	private static final String PERSON 				= "PERSON";
	private static final String PERSON_ID			= "ID";
	private static final String PERSON_NAME			= "NAME";
	private static final String PERSON_EMAIL		= "EMAIL";
	private static final String PERSON_COMMENT		= "COMMENT";
	private static final String PERSON_SCORE		= "SCORE";
	private static final String PERSON_ASSIGNED		= "ASSIGNED";
	private static final String PERSON_PLACE 		= "ASSIGNED_TO";
	private static final String PERSON_PREF			= "PREFERRED";

	//Element tags for the Groups
	private static final String GROUP_LIST 			= "GROUP_LIST";
	private static final String GROUP	 			= "GROUP";
	private static final String GROUP_NAME			= "NAME";
	private static final String GROUP_SCORE			= "SCORE";
	private static final String GROUP_MEMBER		= "MEMBER";
	private static final String MEMBER_ID			= "ID";

	private static final String MAP 				= "MAP";

	private static final String MEMBERSHIP_MANAGER = "MEMBERSHIP";

	
	private File file;
	private Document doc;
	private Mapper map;
	private DocumentBuilderFactory dbFactory;
	private TransformerFactory trFactory;
	
	private HashMap<Person, String> assignations;
	private HashMap<Person, String> preferations;
	
	public FileHandler(String filename) {
		file = new File(filename);
		dbFactory = DocumentBuilderFactory.newInstance();
		trFactory = TransformerFactory.newInstance();
		map = new Mapper();
		
		assignations = new HashMap<Person, String>();
		preferations = new HashMap<Person, String>();
	}
	
	public void setFile(String filename) {
		file = new File(filename);
	}
	
	public void writeMap(Mapper map) 
	throws ParserConfigurationException, TransformerException {
		initDoc();
		
		//the root
		Element root = doc.createElement(MAP);
		doc.appendChild(root);
		
		Element groups 	= doc.createElement(GROUP_LIST);
		Element persons = doc.createElement(PERSON_LIST);
		Element rooms 	= doc.createElement(ROOM_LIST);
		
		appendGroupTree(map.getGroups(), groups);
		appendPersonTree(map.getPersons(), persons);
		appendRoomTree(map.getRooms(), rooms);
		
		root.appendChild(groups);
		root.appendChild(persons);
		root.appendChild(rooms);
		
		doc.normalizeDocument();
		
		writeToFile();
	}
	
	private void readPersons() throws SAXException, IOException,
	ParserConfigurationException, DuplicatePersonException, IllegalNodeException {

		readFile();
		Element persons = doc.getDocumentElement();
		
		if (persons.getNodeName().equals(PERSON_LIST)) {
			NodeList personLists = persons.getElementsByTagName(PERSON_LIST);
			
			if (personLists.getLength() > 0) {
				persons = (Element)personLists.item(0);
			}
			else {
				throw new IllegalNodeException("XML does not include a list of persons");
			}
		}
		
		
		Node next = persons.getFirstChild();
		
		while (next != null) {
			try {
				Person p = readPerson(next);
				map.addPerson(p);
			} catch (IllegalNodeException e) {} 
			next = next.getNextSibling();
		}
		
//		if (!assignations.isEmpty()) {
//			typeAssignations();
//		}
//		if (!preferations.isEmpty()) {
//			typePreferations();
//		}
		
	}

	private void readGroups() throws SAXException, IOException,
	ParserConfigurationException, IllegalNodeException {
		readFile();
		
		Element groups = doc.getDocumentElement();
		if (groups.getNodeName() != GROUP_LIST) {
			NodeList roomList = groups.getElementsByTagName(GROUP_LIST);
			
			if (roomList.getLength() > 0) {
				groups = (Element)roomList.item(0);
			}
			else {
				throw new IllegalNodeException("XML does not include a list of groups");
			}
		}
		
		Node next = groups.getFirstChild();
		
		while (next != null) {
			try {
				Group g = readGroup(next);
				System.out.println("\n"+g.getName()+":");
				List<Person> members = g.getPersons();
				for (Person p : members) {
					System.out.println(p);
				}
			} catch (IllegalNodeException e) {} 
			next = next.getNextSibling();
		}
		
	}

	private void readRooms() throws SAXException, IOException,
	ParserConfigurationException, IllegalNodeException {
		
		readFile();
		
		Element rooms = doc.getDocumentElement();
		if (rooms.getNodeName() != ROOM_LIST) {
			NodeList roomList = rooms.getElementsByTagName(ROOM_LIST);
			
			if (roomList.getLength() > 0) {
				rooms = (Element)roomList.item(0);
			}
			else {
				throw new IllegalNodeException("XML does not include a list of rooms");
			}
		}
		
		Node next = rooms.getFirstChild();
		while (next != null) {
			try {
				Room r = readRoom(next);
				map.addRoom(r);
			} catch (RoomException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IllegalNodeException e) {
				//ignore
			}
			
			next = next.getNextSibling();
		}
		
	}

	public Mapper readMap() throws SAXException, IOException,
	ParserConfigurationException, DuplicatePersonException, IllegalNodeException, PlaceNotFoundException, RoomException, PersonNotFoundException {
		
		
		readRooms(); //now all the rooms and places exist
		readPersons(); //now all the persons exist
		System.out.println("Persons:");
		List<Person> persons = map.getPersons();
		for (Person p : persons) {
			System.out.println(p);
		}
		readGroups(); //now all the persons should have correct scores
		
		assign();
		
		return map;
	}
	
	/**
	 * Writes the list of Rooms to the file set. 
	 * @param rooms The list of Rooms to write to the file.
	 * @throws ParserConfigurationException 
	 * @throws IOException 
	 * @throws TransformerException 
	 */
	public void writeRoomlist(List<Room> rooms) 
	throws ParserConfigurationException, IOException, TransformerException {
		initDoc();
		
		//the root
		Element root = doc.createElement(ROOM_LIST);
		doc.appendChild(root);
		
		appendRoomTree(rooms, root);
		
		doc.normalizeDocument();

		//type to file
		writeToFile();
	}
	
	public void writePersonList(List<Person> persons) 
	throws ParserConfigurationException, TransformerException {
		initDoc();
		
		//the root
		Element root = doc.createElement(PERSON_LIST);
		doc.appendChild(root);
		
		appendPersonTree(persons, root);
		
		doc.normalizeDocument();
		
		//type to file
		writeToFile();
		
	}
	
	public void write(RoomManager mgr) 
	throws ParserConfigurationException, IOException, TransformerException {
		writeRoomlist(mgr.getRooms());
	}
	
	public void write(MembershipManager mgr)
	throws ParserConfigurationException, TransformerException {
		initDoc();
		
		Element root = doc.createElement(MEMBERSHIP_MANAGER);
		
		Element groups = doc.createElement(GROUP_LIST);
		root.appendChild(groups);
		Element persons = doc.createElement(PERSON_LIST);
		root.appendChild(persons);
		
		appendGroupTree(mgr.getGroups(), groups);
		appendPersonTree(mgr.getPersons(), persons);
		
		writeToFile();
		
		
	}
	
	public void writeGroupList(List<Group> groups) 
	throws ParserConfigurationException, TransformerException {
		initDoc();
		
		//the root
		Element root = doc.createElement(GROUP_LIST);
		doc.appendChild(root);
		
		appendGroupTree(groups, root);
		
		doc.normalizeDocument();
		
		//type to file
		writeToFile();
	}
	
	private void appendRoomTree(List<Room> rooms, Element parent) {
		//iterate through the rooms
		for (Room r : rooms) {
			Element elRoom = element(r);
			
			parent.appendChild(elRoom);
		}
	}

	private void appendPersonTree(List<Person> persons, Element parent) {
		//iterate through the Persons
		for (Person p : persons) {
			Element elPerson = element(p);
			
			parent.appendChild(elPerson);
		}
	}

	private void appendGroupTree(List<Group> groups, Node parent) {
		// iterate through the Groups
		for (Group g : groups) {
			Element elGroup = element(g);
			
			parent.appendChild(elGroup);
		}
	}

	private Place readPlace(Node placeNode, Room r) throws IllegalNodeException {
		if (placeNode.getNodeName() != PLACE) {
			throw new IllegalNodeException("Place is not place");
		}
		NamedNodeMap attrs = placeNode.getAttributes();
		
		
		int id = Integer.parseInt(attrs.getNamedItem(PLACE_ID).getNodeValue());
		boolean free = attrs.getNamedItem(PLACE_FREE).getNodeValue().endsWith("true");
		boolean reserved = attrs.getNamedItem(PLACE_RESERVED).getNodeValue().endsWith("true");
		
		Place pl = new Place(r, id);
		
		if (!free) {
			//todo: should import the history, for now we assume the Persons are re-assigned correctly
		}
		if (reserved) {
			Node reservationList = placeNode.getFirstChild();
			while (reservationList != null) {
				try {
					readReservations(placeNode.getFirstChild(), pl);
					break;
				} catch (IllegalNodeException e) {
					// ignore
				}
				reservationList = reservationList.getNextSibling();
			}
		}
	
		return pl;
	}

	private Group readGroup(Node groupNode) throws IllegalNodeException {
		if (groupNode.getNodeName() != GROUP) {
			throw new IllegalNodeException("Group node is not a Group");
		}
		NamedNodeMap attrs = groupNode.getAttributes();
		
		String name = attrs.getNamedItem(GROUP_NAME).getNodeValue();
		int score = Integer.parseInt(attrs.getNamedItem(GROUP_SCORE).getNodeValue());
		
		Group g = new Group(name, score);
		
		//iterate through the members
		Node next = groupNode.getFirstChild();
		while (next != null) {
			if (next.getNodeName() == GROUP_MEMBER) {
				int id = Integer.parseInt(next.getAttributes().getNamedItem(MEMBER_ID).getNodeValue());
				try {
					Person p = map.getPerson(id);
					System.out.println("Before adding to "+name+": " +p);
					g.addPerson(map.getPerson(id));
					System.out.println("After adding to "+name+": " +p);
				} catch (PersonNotFoundException e) {
					// shouldn't happen
					System.out.println("Could not add person " + id + " to " + g);
					e.printStackTrace();
				}
			
			}
			next = next.getNextSibling();
		}
		
		return g;
	}

	private Person readPerson(Node personNode) throws IllegalNodeException {
		if (personNode.getNodeName() != PERSON) {
			throw new IllegalNodeException("Person node is not a Person");
		}
		
		NamedNodeMap attrs = personNode.getAttributes();
		
		int id = Integer.parseInt(attrs.getNamedItem(PERSON_ID).getNodeValue());
		String name = attrs.getNamedItem(PERSON_NAME).getNodeValue();
		String mail = attrs.getNamedItem(PERSON_EMAIL).getNodeValue();
		int score =  Integer.parseInt(attrs.getNamedItem(PERSON_SCORE).getNodeValue());
		boolean assigned = attrs.getNamedItem(PERSON_ASSIGNED).getNodeValue().endsWith("true");
		String place = "";
		String pref = null;
		if (assigned) {
			place = attrs.getNamedItem(PERSON_PLACE).getNodeValue();
		} 
		if (attrs.getNamedItem(PERSON_PREF) != null) {
			pref = attrs.getNamedItem(PERSON_PREF).getNodeValue();
		}
		
		
		
		//get the comment child
		String comment = "";
		Node child = personNode.getFirstChild();
		while (child != null) {
			if (child.getNodeName() == PERSON_COMMENT) {
				comment = child.getTextContent();
			}
			child = child.getNextSibling();
		}
	
		//FIXME This causes the first- and surname to both be the full name - rewrite this!
		Person p = new Person(id, name, name, mail, comment);
		
		if (assigned) {
			assignList(p, place);
		}
		if (pref != null) {
			prefList(p, pref);
		}
		
		return p;
	}

	private Room readRoom(Node roomNode) throws IllegalNodeException {
		if (roomNode.getNodeName() != ROOM) {
			throw new IllegalNodeException("Room node is not a Room");
		}
		NamedNodeMap attrs = roomNode.getAttributes();
		
		int id = Integer.parseInt(attrs.getNamedItem(ROOM_ID).getNodeValue());
		String name = attrs.getNamedItem(ROOM_NAME).getNodeValue().trim();
		
		Room r = new Room(id, name);
		
		//iterate through the places, and add them to the room
		Node placeNode = roomNode.getFirstChild();
		
		while (placeNode != null) {
			try {
				Place pl = readPlace(placeNode, r);
				r.addPlace(pl);
			} catch (IllegalNodeException e) {
				//ignore
			} catch (PlaceNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			placeNode = placeNode.getNextSibling();
		}
	
		
		return r;
	}

	private void readReservations(Node resListNode, Place pl) throws IllegalNodeException {
		if (resListNode.getNodeName() != RESERVATION_LIST) {
			throw new IllegalNodeException("Reservationlist not a reservationlist");
		}
		//TODO implement reading reservations
		
	}

	private Element element(Group g) {
		Element elGroup = doc.createElement(GROUP);
		elGroup.setAttribute(GROUP_NAME, g.getName());
		elGroup.setAttribute(GROUP_SCORE, ""+g.getScore());
		
		List<Person> liMembers = g.getPersons();
		
		for(Person p : liMembers) {
			Element elMember = doc.createElement(GROUP_MEMBER);
			elMember.setAttribute(MEMBER_ID, ""+p.getID());
			
			elGroup.appendChild(elMember);
		}
		
		
		return elGroup;
	}

	private Element element(Person p) {
		Element elPerson = doc.createElement(PERSON);
		elPerson.setAttribute(PERSON_ID, ""+p.getID());
		elPerson.setAttribute(PERSON_NAME, p.getName());
		elPerson.setAttribute(PERSON_EMAIL, p.getEmail());
		elPerson.setAttribute(PERSON_SCORE, "" + p.getScore());
		elPerson.setAttribute(PERSON_ASSIGNED, ""+p.isAssigned());
		if (p.isAssigned()) {
			elPerson.setAttribute(PERSON_PLACE, p.getPlace().getPlaceName());
		} else {
			elPerson.setAttribute(PERSON_PLACE, "N/A");
			
		}
		if (p.hasPreferredPlace()) {
			elPerson.setAttribute(PERSON_PREF, p.getPreferredPlace().getPlaceName());
		}
		
		//add the comment for the Person
		Element elComment = doc.createElement(PERSON_COMMENT);
		elComment.appendChild(doc.createTextNode(p.getComment()));
		elPerson.appendChild(elComment);

		
		return elPerson;
	}

	private Element element(Room r) {
		Element elRoom = doc.createElement(ROOM);
		elRoom.setAttribute(ROOM_ID, ""+r.getID());
		elRoom.setAttribute(ROOM_NAME, r.getName());
		
		List<Place> liPlaces = r.getPlaces();
		
		for (Place pl : liPlaces) {
			Element elPlace = element(pl);
			
			elRoom.appendChild(elPlace);
		}
		
		return elRoom;
	}

	private Element element(Place pl) {
		Element elPlace = doc.createElement(PLACE);
		elPlace.setAttribute(PLACE_ID, ""+pl.getPlaceID());
		elPlace.setAttribute(PLACE_FREE, ""+pl.isFree());
		elPlace.setAttribute(PLACE_RESERVED, ""+pl.isReserved());
		if (pl.isPreferredBy()) {
			elPlace.setAttribute(PLACE_PREF, ""+pl.getPreferredBy().getID());
		} else {
			elPlace.setAttribute(PLACE_PREF, "0");
		}
		
		//Add the history
		Element elHistory = historyElement(pl);
		elPlace.appendChild(elHistory);
		
		//Add the reservations
		Element elReservations = reservationsElement(pl);
		elPlace.appendChild(elReservations);
		
		return elPlace;
	}

	private Element reservationsElement(Place pl) {
		Element elResList = doc.createElement(RESERVATION_LIST);
		
		try {
			List<Reservation> reservations = pl.getReservations();
		
			for (Reservation res : reservations) {
				Element elReservation = doc.createElement(RESERVATION);
				
				List<Group> liGroups = res.getGroups();
				for (Group g : liGroups) {
					Element elGroup = doc.createElement(RES_GROUP);
					elGroup.setAttribute(RES_GROUP_ID, g.getName());
					
					elReservation.appendChild(elGroup);
				}
				
				List<Person> liPersons = res.getPersons();
				for (Person p : liPersons) {
					Element elPers = doc.createElement(RESERVATION_PERSON);
					elPers.setAttribute(RES_PERSON_ID, ""+p.getID());
					
					elReservation.appendChild(elPers);
				}
			}
			
			
			return elResList;
		} catch (ReservationConflictException e) {
			return elResList;
		}
	}

	private Element historyElement(Place pl) {
		Element elHistory = doc.createElement(HISTORY);

		List<Person> liHistory = pl.getHistory();
		int n = 0;
		for (Person p : liHistory) {
			Element elPerson = doc.createElement(PREVIOUS_OWNER);
			elPerson.setAttribute(PREV_OWN_SEQ, ""+ ++n);
			elPerson.setAttribute(PREV_OWN_ID, ""+p.getID());
			
			elHistory.appendChild(elPerson);
		}
		
		return elHistory;
	}


	/**
	 * Makes the document
	 * @throws ParserConfigurationException 
	 *
	 */
	private void initDoc() throws ParserConfigurationException {
		if (doc == null) {
			DocumentBuilder builder = dbFactory.newDocumentBuilder();
			doc = builder.newDocument();
		}
		
	}
	
	private void readFile() throws SAXException, IOException, ParserConfigurationException {
		DocumentBuilder builder = dbFactory.newDocumentBuilder();
		doc = builder.parse(file);
		
	}

	private void writeToFile() throws TransformerException {
		DOMSource source = new DOMSource(doc);
		
		Transformer optimusPrime = trFactory.newTransformer();
		optimusPrime.setOutputProperty(OutputKeys.INDENT, "yes");
		
		StreamResult result = new StreamResult(file);
		
		optimusPrime.transform(source, result);
	}

	private void prefList(Person p, String pref) {
		preferations.put(p, pref);
		
	}

	/**
	 * Store the Person in a list, for iteration once all Places are made.
	 * @param p The Person who's assigned
	 * @param place The String-representation of the assigned place
	 */
	private void assignList(Person p, String place) {
		assignations.put(p, place);
		
	}

	private void typeAssignations() {
		Iterator<Entry<Person, String>> it = assignations.entrySet().iterator();
		System.out.println("Assignations:");
		while (it.hasNext()) {
			Entry<Person, String> e = it.next();
			System.out.println(e.getKey().getName() + ": " + e.getValue());
		}
		
	}

	private void typePreferations() {
		Iterator<Entry<Person, String>> it = preferations.entrySet().iterator();
		System.out.println("Preferations:");
		while (it.hasNext()) {
			Entry<Person, String> e = it.next();
			System.out.println(e.getKey().getName() + ": " + e.getValue());
		}
		
	}

	private void assign() throws PlaceNotFoundException, RoomException, PersonNotFoundException {
		Iterator<Entry<Person, String>> it = assignations.entrySet().iterator();
	
		while (it.hasNext()) {
			Entry<Person, String> e = it.next();
			map.assignPersonToPlace(e.getKey(), map.getPlace(e.getValue()));
		}
		
	}
}
